/*
  HardwareSerial.cpp - Hardware serial library for Wiring
  Copyright (c) 2006 Nicholas Zambetti.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  
  Modified 23 November 2006 by David A. Mellis
  Modified 28 September 2010 by Mark Sproul
  Modified 14 August 2012 by Alarus
  Modified 3 December 2013 by Matthijs Kooijman
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "Arduino.h"

#include "HardwareSerial.h"

// this next line disables the entire HardwareSerial.cpp, 
// this is so I can support Attiny series and any other chip without a uart
#if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3)

// SerialEvent functions are weak, so when the user doesn't define them,
// the linker just sets their address to 0 (which is checked below).
// The Serialx_available is just a wrapper around Serialx.available(),
// but we can refer to it weakly so we don't pull in the entire
// HardwareSerial instance if the user doesn't also refer to it.
#if defined(HAVE_HWSERIAL0)
  HardwareSerial Serial(SERIAL0_INDEX);
  WEAKFUNC_HEAD void serialEvent() WEAKFUNC_TAIL {}
  WEAKFUNC_HEAD bool Serial0_available() WEAKFUNC_TAIL { return true; }
#endif

#if defined(HAVE_HWSERIAL1)
  HardwareSerial Serial1(SERIAL1_INDEX);
  WEAKFUNC_HEAD void serialEvent1() WEAKFUNC_TAIL {}
  WEAKFUNC_HEAD bool Serial1_available() WEAKFUNC_TAIL { return true; }
#endif

#if defined(HAVE_HWSERIAL2)
  HardwareSerial Serial2(SERIAL2_INDEX);
  WEAKFUNC_HEAD void serialEvent2() WEAKFUNC_HEAD {}
  WEAKFUNC_HEAD bool Serial2_available() WEAKFUNC_HEAD { return true; }
#endif

#if defined(HAVE_HWSERIAL3)
  HardwareSerial Serial3(SERIAL3_INDEX);
  WEAKFUNC_HEAD void serialEvent3() WEAKFUNC_TAIL {}
  WEAKFUNC_HEAD bool Serial3_available() WEAKFUNC_TAIL { return true; }
#endif

void serialEventRun(void)
{
#if defined(HAVE_HWSERIAL0)
  if (Serial0_available && serialEvent && Serial0_available()) serialEvent();
#endif
#if defined(HAVE_HWSERIAL1)
  if (Serial1_available && serialEvent1 && Serial1_available()) serialEvent1();
#endif
#if defined(HAVE_HWSERIAL2)
  if (Serial2_available && serialEvent2 && Serial2_available()) serialEvent2();
#endif
#if defined(HAVE_HWSERIAL3)
  if (Serial3_available && serialEvent3 && Serial3_available()) serialEvent3();
#endif
}

// Actual interrupt handlers //////////////////////////////////////////////////////////////

static void tx_complete_irq(void *p)
{
  struct vsfard_uart_t *uart = (struct vsfard_uart_t *)p;
  while (vsfhal_usart_tx_get_free_size(uart->index)) {
    unsigned char c = uart->tx_buffer[uart->tx_buffer_tail];
    uart->tx_buffer_tail = (uart->tx_buffer_tail + 1) % SERIAL_TX_BUFFER_SIZE;
    vsfhal_usart_tx_bytes(uart->index, &c, 1);
    if (uart->tx_buffer_head == uart->tx_buffer_tail)
      break;
  }

  vsfhal_usart_tx_int_config(uart->index, uart->tx_buffer_head != uart->tx_buffer_tail);
  if (uart->sm != NULL) {
    struct vsfsm_t *sm = uart->sm;
    uart->sm = NULL;
    vsfsm_post_evt(sm, VSFSM_EVT_USER);
  }
}

static void rx_complete_irq(void *p)
{
  struct vsfard_uart_t *uart = (struct vsfard_uart_t *)p;
  // No Parity error, read byte and store it in the buffer if there is room
  unsigned char c;
  rx_buffer_index_t i;

  while (vsfhal_usart_rx_get_data_size(uart->index)) {
    vsfhal_usart_rx_bytes(uart->index, &c, 1);

    i = (unsigned int)(uart->rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE;

	// if we should be storing the received character into the location
    // just before the tail (meaning that the head would advance to the
    // current location of the tail), we're about to overflow the buffer
    // and so we don't write the character or advance the head.
    if (i != uart->rx_buffer_tail) {
      uart->rx_buffer[uart->rx_buffer_head] = c;
      uart->rx_buffer_head = i;
    }
  }
}

// Constructors ////////////////////////////////////////////////////////////////

HardwareSerial::HardwareSerial(vsfhal_usart_t index)
{
  _uart.index = index;
}

// Public Methods //////////////////////////////////////////////////////////////

void HardwareSerial::begin(unsigned long baud, byte config)
{
  vsfhal_usart_init(_uart.index);
  vsfhal_usart_config_cb(_uart.index, 0xFF, &_uart, tx_complete_irq, rx_complete_irq);
  vsfhal_usart_config(_uart.index, baud, config);
  _uart.written = false;
}

void HardwareSerial::end()
{
  // wait for transmission of outgoing data
  flush();
  vsfhal_usart_fini(_uart.index);

  // clear any received data
  _uart.rx_buffer_head = _uart.rx_buffer_tail;
}

int HardwareSerial::available(void)
{
  return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _uart.rx_buffer_head - _uart.rx_buffer_tail)) % SERIAL_RX_BUFFER_SIZE;
}

int HardwareSerial::peek(void)
{
  if (_uart.rx_buffer_head == _uart.rx_buffer_tail) {
    return -1;
  } else {
    return _uart.rx_buffer[_uart.rx_buffer_tail];
  }
}

int HardwareSerial::read(void)
{
  // if the head isn't ahead of the tail, we don't have any characters
  if (_uart.rx_buffer_head == _uart.rx_buffer_tail) {
    return -1;
  } else {
    unsigned char c = _uart.rx_buffer[_uart.rx_buffer_tail];
    _uart.rx_buffer_tail = (rx_buffer_index_t)(_uart.rx_buffer_tail + 1) % SERIAL_RX_BUFFER_SIZE;
    return c;
  }
}

int HardwareSerial::availableForWrite(void)
{
  tx_buffer_index_t head = _uart.tx_buffer_head;
  tx_buffer_index_t tail = _uart.tx_buffer_tail;

  if (head >= tail) return SERIAL_TX_BUFFER_SIZE - 1 - head + tail;
  return tail - head - 1;
}

void HardwareSerial::flush()
{
  // If we have never written a byte, no need to flush. This special
  // case is needed since there is no way to force the TXC (transmit
  // complete) bit to 1 during initialization
  if (!_uart.written)
    return;

  vsfhal_usart_tx_int_config(_uart.index, false);
  while (_uart.tx_buffer_head != _uart.tx_buffer_tail) {
    _uart.sm = &(vsfsm_thread_get_cur())->sm;
    vsfhal_usart_tx_int_config(_uart.index, true);
    vsfsm_thread_wfe(VSFSM_EVT_USER);
  }
  // If we get here, nothing is queued anymore (DRIE is disabled) and
  // the hardware finished tranmission (TXC is set).
}

size_t HardwareSerial::write(uint8_t c)
{
  _uart.written = true;

  tx_buffer_index_t curhead = _uart.tx_buffer_head;
  tx_buffer_index_t nxthead = (curhead + 1) % SERIAL_TX_BUFFER_SIZE;
	
  // If the output buffer is full, there's nothing for it other than to 
  // wait for the interrupt handler to empty it a bit
  vsfhal_usart_tx_int_config(_uart.index, false);
  if (nxthead == _uart.tx_buffer_tail) {
    _uart.sm = &(vsfsm_thread_get_cur())->sm;
    vsfhal_usart_tx_int_config(_uart.index, true);
    vsfsm_thread_wfe(VSFSM_EVT_USER);
  }

  vsfhal_usart_tx_int_config(_uart.index, false);
  _uart.tx_buffer[_uart.tx_buffer_head] = c;
  _uart.tx_buffer_head = nxthead;
  if (curhead == _uart.tx_buffer_tail) {
    tx_complete_irq(&_uart);
  } else {
    vsfhal_usart_tx_int_config(_uart.index, true);
  }

  return 1;
}

#endif // whole file
